OOP calling convention for database functions. DBMS abstraction implemented by means...
[lhc/web/wiklou.git] / includes / LoadBalancer.php
1 <?php
2 # Database load balancing object
3
4 require_once( "Database.php" );
5
6 # Valid database indexes
7 # Operation-based indexes
8 define( "DB_READ", -1 ); # Read from the slave (or only server)
9 define( "DB_WRITE", -2 ); # Write to master (or only server)
10 define( "DB_LAST", -3 ); # Whatever database was used last
11
12 # Task-based indexes
13 # ***NOT USED YET, EXPERIMENTAL***
14 # These may be defined in $wgDBservers. If they aren't, the default reader or writer will be used
15 # Even numbers are always readers, odd numbers are writers
16 define( "DB_TASK_FIRST", 1000 ); # First in list
17 define( "DB_SEARCH_R", 1000 ); # Search read
18 define( "DB_SEARCH_W", 1001 ); # Search write
19 define( "DB_ASKSQL_R", 1002 ); # Special:Asksql read
20 define( "DB_WATCHLIST_R", 1004 ); # Watchlist read
21 define( "DB_TASK_LAST", 1004) ; # Last in list
22
23 class LoadBalancer {
24 /* private */ var $mServers, $mConnections, $mLoads;
25 /* private */ var $mFailFunction;
26 /* private */ var $mForce, $mReadIndex, $mLastConn;
27
28 function LoadBalancer()
29 {
30 $this->mServers = array();
31 $this->mConnections = array();
32 $this->mFailFunction = false;
33 $this->mReadIndex = -1;
34 $this->mForce = -1;
35 $this->mLastConn = false;
36 }
37
38 function newFromParams( $servers, $failFunction = false )
39 {
40 $lb = new LoadBalancer;
41 $lb->initialise( $servers, $failFunction = false );
42 return $lb;
43 }
44
45 function initialise( $servers, $failFunction = false )
46 {
47 $this->mServers = $servers;
48 $this->mFailFunction = $failFunction;
49 $this->mReadIndex = -1;
50 $this->mWriteIndex = -1;
51 $this->mForce = -1;
52 $this->mConnections = array();
53 $this->mLastConn = false;
54 $this->mLoads = array();
55
56 foreach( $servers as $i => $server ) {
57 $this->mLoads[$i] = $server['load'];
58 }
59
60 wfSeedRandom();
61 }
62
63 # Given an array of non-normalised probabilities, this function will select
64 # an element and return the appropriate key
65 function pickRandom( $weights )
66 {
67 if ( !is_array( $weights ) || count( $weights ) == 0 ) {
68 return false;
69 }
70
71 $sum = 0;
72 foreach ( $weights as $w ) {
73 $sum += $w;
74 }
75 $max = mt_getrandmax();
76 $rand = mt_rand(0, $max) / $max * $sum;
77
78 $sum = 0;
79 foreach ( $weights as $i => $w ) {
80 $sum += $w;
81 if ( $sum >= $rand ) {
82 break;
83 }
84 }
85 return $i;
86 }
87
88 function &getReader()
89 {
90 if ( $this->mForce >= 0 ) {
91 $conn =& $this->getConnection( $this->mForce );
92 } else {
93 if ( $this->mReadIndex >= 0 ) {
94 $conn =& $this->getConnection( $this->mReadIndex );
95 } else {
96 # $loads is $this->mLoads except with elements knocked out if they
97 # don't work
98 $loads = $this->mLoads;
99 do {
100 $i = $this->pickRandom( $loads );
101 if ( $i !== false ) {
102 wfDebug( "Using reader #$i: {$this->mServers[$i]['host']}\n" );
103
104 $conn =& $this->getConnection( $i );
105 if ( !$conn->isOpen() ) {
106 unset( $loads[$i] );
107 }
108 }
109 } while ( $i !== false && !$conn->isOpen() );
110 if ( $conn->isOpen() ) {
111 $this->mReadIndex = $i;
112 }
113 }
114 }
115 if ( $conn === false || !$conn->isOpen() ) {
116 $this->reportConnectionError( $conn );
117 $conn = false;
118 }
119 return $conn;
120 }
121
122 function &getConnection( $i, $fail = false )
123 {
124 /*
125 # Task-based index
126 if ( $i >= DB_TASK_FIRST && $i < DB_TASK_LAST ) {
127 if ( $i % 2 ) {
128 # Odd index use writer
129 $i = DB_WRITE;
130 } else {
131 # Even index use reader
132 $i = DB_READ;
133 }
134 }*/
135
136 # Operation-based index
137 # Note, getReader() and getWriter() will re-enter this function
138 if ( $i == DB_READ ) {
139 $this->mLastConn =& $this->getReader();
140 } elseif ( $i == DB_WRITE ) {
141 $this->mLastConn =& $this->getWriter();
142 } elseif ( $i == DB_LAST ) {
143 # Just use $this->mLastConn, which should already be set
144 if ( $this->mLastConn === false ) {
145 # Oh dear, not set, best to use the writer for safety
146 $this->mLastConn =& $this->getWriter();
147 }
148 } else {
149 # Explicit index
150 if ( !array_key_exists( $i, $this->mConnections) || !$this->mConnections[$i]->isOpen() ) {
151 $this->mConnections[$i] = $this->makeConnection( $this->mServers[$i] );
152 }
153 if ( !$this->mConnections[$i]->isOpen() ) {
154 wfDebug( "Failed to connect to database $i at {$this->mServers[$i]['host']}\n" );
155 if ( $fail ) {
156 $this->reportConnectionError( $this->mConnections[$i] );
157 }
158 $this->mConnections[$i] = false;
159 }
160 $this->mLastConn =& $this->mConnections[$i];
161 }
162 return $this->mLastConn;
163 }
164
165 /* private */ function makeConnection( &$server ) {
166 extract( $server );
167 # Get class for this database type
168 $class = 'Database' . ucfirst( $type );
169 if ( !class_exists( $class ) ) {
170 require_once( "$class.php" );
171 }
172
173 # Create object
174 return new $class( $host, $user, $password, $dbname, 1 );
175 }
176
177 function reportConnectionError( &$conn )
178 {
179 if ( !is_object( $conn ) ) {
180 $conn = new Database;
181 }
182 if ( $this->mFailFunction ) {
183 $conn->setFailFunction( $this->mFailFunction );
184 } else {
185 $conn->setFailFunction( "wfEmergencyAbort" );
186 }
187 $conn->reportConnectionError();
188 }
189
190 function &getWriter()
191 {
192 $c =& $this->getConnection( 0 );
193 if ( $c === false || !$c->isOpen() ) {
194 $this->reportConnectionError( $c );
195 $c = false;
196 }
197 return $c;
198 }
199
200 function force( $i )
201 {
202 $this->mForce = $i;
203 }
204
205 function haveIndex( $i )
206 {
207 return array_key_exists( $i, $this->mServers );
208 }
209 }